﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace SanctusSoftware.Logging.WSUpload
{
    public class WSUploader
    {

        private WSUploader()
        {
            this.ChunkSize = (1024 * 32); // Default Chunk Size
        }

        public WSUploader(string targetFileName, FileInfo sourceFile)
            : this()
        {
            this.TargetFileName = targetFileName;
            TargetFileContents = File.ReadAllBytes(sourceFile.FullName);
            
        }

        public WSUploader(string targetFileName, byte[] targetFileContents)
            : this()
        {
            this.TargetFileName = targetFileName;
            this.TargetFileContents = targetFileContents;
        }

        #region Public Properties
        
        public byte[] TargetFileContents { get; private set; }
        public string TargetFileName { get; private set; }
        public int ChunkSize { get; set; }

        public int NumberOfChunks
        {
            get
            {
                if (ChunkSize == 0)
                {
	                 return 0;
                }
                decimal numChunks = Convert.ToDecimal(TargetFileContents.Length) / Convert.ToDecimal(ChunkSize);
                return Convert.ToInt32(Math.Round(numChunks + 0.5M));
            }
        }

        #endregion

        #region Public Methods

        public void Upload()
        {

            int percent = 0;

            LogUploadWS.Service service = null;

            try
            {
                OnUploadStatusChanged("Instantiating Web Service Reference", percent);
                service = new LogUploadWS.Service();    
            }
            catch (Exception ex)
            {
                OnUploadStatusChanged("Failed to Instantiate Web Service Reference", percent);
                OnUploadFailed();
                return;
            }
            
            string token = string.Empty;

            try
            {
                OnUploadStatusChanged("Retrieving Token", percent);
                token = service.GetToken();
                OnUploadStatusChanged("Token Retrieved", percent);
            }
            catch (Exception ex)
            {
                //throw new Exception("Failed To Retrieve Token");
                OnUploadStatusChanged("Failed to Retrieve Token", percent);
                OnUploadFailed();
                return;
            }

            List<Byte> bytes = null;

            try
            {
                OnUploadStatusChanged("Generating Data", percent);
                bytes = new List<byte>();
                bytes.AddRange(this.TargetFileContents);
            }
            catch (Exception ex)
            {
                OnUploadStatusChanged("Failed to Retrieve Token", percent);
                OnUploadFailed();
                return;
            }


            int chunkNumber = 0;


            for (int i = 0; i < bytes.Count; i += ChunkSize)
            {

                chunkNumber++;
                int tempChunkSize = 0;

                try
                {
                    OnUploadStatusChanged("Determining Chunk Size", percent);
                    tempChunkSize = ChunkSize;
                    if (i + ChunkSize > bytes.Count)
                    {
                        tempChunkSize = bytes.Count - i;
                    }
                }
                catch (Exception ex)
                {
                    OnUploadStatusChanged("Failed to Determine Chunk Size", percent);
                    OnUploadFailed();
                    return;
                }

                OnUploadStatusChanged("Retrieving Chunk " + chunkNumber.ToString() + " of " + this.NumberOfChunks.ToString(), percent);
                byte[] chunk = new byte[tempChunkSize];
                bytes.CopyTo(i, chunk, 0, tempChunkSize);

                bool cancel = false;
                OnUploadingChunk(out cancel);
                if (cancel)
                {
                    OnUploadStatusChanged("Canceling Token");
                    service.Cancel(token);
                    OnUploadCanceled();
                    OnUploadStatusChanged("Upload Canceled");
                    return;
                }


                try
                {
                    OnUploadStatusChanged("Uploading Chunk " + chunkNumber.ToString() + " of " + this.NumberOfChunks.ToString(), percent);
                    service.SubmitFileChunk(token, chunk, this.TargetFileName, chunkNumber);

                }
                catch (Exception ex)
                {
                    OnUploadStatusChanged("Failed to Upload Chunk " + chunkNumber.ToString() + " of " + this.NumberOfChunks.ToString(), percent);
                    OnUploadFailed();
                    return;
                }
                percent = Convert.ToInt32(Convert.ToDecimal(chunkNumber) / Convert.ToDecimal(this.NumberOfChunks) * 100.0M);
                OnChunkUploaded(chunkNumber);
                OnUploadStatusChanged("Uploaded Chunk " + chunkNumber.ToString() + " of " + this.NumberOfChunks.ToString(), percent);

                //verifyBytes.AddRange(chunk);

            }

            //if (verifyBytes.Count != bytes.Count)
            //{
            //    Console.WriteLine("Boom!");
            //}
            //else
            //{
            //    Console.WriteLine("Yes!");
            //}

            try
            {
                OnUploadStatusChanged("Assembling Chunks", percent);
                service.AssembleFile(token, TargetFileName);
            }
            catch (Exception ex)
            {
                OnUploadStatusChanged("Failed to Assemble Chunks", percent);
                OnUploadFailed();
                return;
            }

            try
            {
                OnUploadStatusChanged("Cleaning Up Chunks", percent);
                service.CleanUpChunks(token, TargetFileName);
            }
            catch (Exception ex)
            {
                OnUploadStatusChanged("Failed to Clean Up Chunks", percent);
                OnUploadFailed();
                return;
            }

            OnUploadStatusChanged("Upload Completed", percent);
            OnUploadCompleted();

        }

        #endregion

        #region Public Events

        public event EventHandler<UploadStatusEventArgs> UploadStatusChanged;

        protected void OnUploadStatusChanged(string status)
        {
            OnUploadStatusChanged(status, 0);
        }

        protected void OnUploadStatusChanged(string status, int percent)
        {
            if (UploadStatusChanged != null)
            {
                UploadStatusChanged(this, new UploadStatusEventArgs(status, percent));
            }
        }

        public event EventHandler<UploadingChunkEventArgs> UploadingChunk;

        protected void OnUploadingChunk(out bool cancel)
        {
            if (UploadingChunk != null)
            {
                UploadingChunkEventArgs e = new UploadingChunkEventArgs();
                UploadingChunk(this, e);
                cancel = e.Cancel;
            }
            else
            {
                cancel = false;
            }
            
        }

        public event EventHandler<UploadChunkEventArgs> ChunkUploaded;

        protected void OnChunkUploaded(int chunkNumber)
        {
            if (ChunkUploaded != null)
            {
                ChunkUploaded(this, new UploadChunkEventArgs(chunkNumber, this.NumberOfChunks));
            }
        }

        public event EventHandler UploadCanceled;

        protected void OnUploadCanceled()
        {
            if (UploadCanceled != null)
            {
                UploadCanceled(this, new EventArgs());
            }
        }

        public event EventHandler UploadFailed;

        protected void OnUploadFailed()
        {
            if (UploadFailed != null)
            {
                UploadFailed(this, new EventArgs());
            }
        }


        public event EventHandler UploadCompleted;

        protected void OnUploadCompleted()
        {
            if (UploadCompleted != null)
            {
                UploadCompleted(this, new EventArgs());
            }
        }

        #endregion

    }
}
